// Module:	variant.d: A stack-based variant implemention
// Author:	Li Wei (oldrev@gmail.com)
// License:	GPL

module dayudict.util.variant;

import std.typetuple;


private template MaxSizeImp(T, V...)
{
    static if(V.length > 0)
        private const int tailResult = MaxSizeImp!(V).result;
    else 
        private const int tailResult = T.sizeof;

    public const int result = T.sizeof > tailResult ? T.sizeof : tailResult;
};

private template MaxSize(TList...)
{
    const int MaxSize = MaxSizeImp!(TList).result;
}


/**
  \brief A Variant implemention inspired by boost.variant
*/
struct Variant(TList...)
{
    public alias TList TypeList;
    public alias Variant!(TypeList) SelfType;
	private alias ubyte[MaxSize!(TypeList)] Holder;

    private Holder  m_held;
    private int     m_which = -1;

	template Which(T)
	{
		const int Which = IndexOf!(T, TypeList);
	}

    public static SelfType opCall(ValueType)(ValueType val)
    {
        SelfType var;
        var = val;
        return var;
    }

    public int which()
    {
        return m_which;
    }

	public SelfType assign(ValueType)(ValueType val)
	{
		static if(is(ValueType == SelfType))
		{
            m_held[] = val.m_held[];
            m_which = val.which;
		}		
		else
		{
			const int i = IndexOf!(ValueType, TypeList);
			static assert(i >= 0);
			ValueType* heldPtr = cast(ValueType*)m_held.ptr;
			*heldPtr = val;
			m_which = i;
		}

		return *this;
	}

    public SelfType opAssign(ValueType)(ValueType rhs)
    {
        return assign!(ValueType)(rhs);
    }

	public bool opEquals(ValueType)(ValueType rhs)
	{
        assert(!empty);

		static if(is(ValueType == SelfType))
		{
			foreach(T; TypeList)
			{
				const int i = IndexOf!(T, TypeList);
				if(i == which)
				{
					return (rhs.which == which) && (get!(T) == rhs.get!(T));
				}
			}
		}
		else
		{
			const int i = IndexOf!(ValueType, TypeList);
			static assert(i >= 0);
		
			return get!(ValueType)() == rhs;
		}
	}


	public int opCmp(ValueType)(ValueType rhs) //used for hash or something else
	{
		if(rhs == *this)return 0;

		static if(is(ValueType == SelfType))
		{
			foreach(T; TypeList)
			{
				const int i = IndexOf!(T, TypeList);
				if((i == which) && (rhs.which == which))
				{
					return get!(T) < rhs.get!(T) ? -1 : 1;
				}
			}
		}
		else
		{
			const int i = IndexOf!(ValueType, TypeList);
			static assert(i >= 0);
		
			return get!(ValueType)() < rhs ? -1 : 1;
		}
	}
	
	public TypeInfo type()
	{
		foreach(T; TypeList)
		{
			const int i = IndexOf!(T, TypeList);
			if(i == which)
			{
				return typeid(TypeList[i]);
			}
		}
	}

    public ValueType get(ValueType)()
    {		
        /*if(IndexOf!(ValueType, TypeList) != which) 
			throw new BadCastException(typeid(ValueType).toString ~ " is not a valided type in the TypeList");*/
        assert(IndexOf!(ValueType, TypeList) == which) ;

        return *(cast(ValueType*)m_held.ptr);
    }

    public bool empty()
    {
        return m_which < 0;
    }

	public void swap(inout SelfType var)
	{
		Holder h;
		h[] = m_held[];
		int w = which;
		m_held[] = var.m_held[];
		m_which = var.which;
		var.m_held[] = h[];
		var.m_which = w;
	}

}



// unit test
debug(DD_Unittest) unittest
{
    class Foo
    {
		public:
		int n = 0;
		int x = 0;
		int y = 0;
		int z = 0;

		Foo opAssign(int rhs)
		{
            x = rhs;
            y = rhs;
			z = rhs;
			return this;
		}
    }

    struct Bar
    {
        public:
        int x = 0;
        int y = 1;
        int z = 2;
    }
	Variant!(int) v0;
	assert(v0.empty);

    Variant!(double, int, char[], Foo, Bar) v = 2; // DMD 0.177 new feature
    assert(v == 2);
	assert(v <= 2);
    v = 2.22;
    assert(v == 2.22);
	assert(v.type == typeid(double));
	v = new Foo;
	assert(v.type == typeid(Foo));
	v.get!(Foo)() = 2;
	assert(v.get!(Foo)().z == 2);
	typeof(v) v2 = v;
	assert(v2 == v);
	v = "Foobar".dup;
	assert(v == "Foobar".dup);
	
	v.swap(v2);
	assert(v2.get!(char[])() == "Foobar");
    
    Bar b;
    b.x = 10;
    v = b;
    assert(v.get!(Bar)().x == 10);

}
